package br.upe.dsc.caeto.simulation;

import br.upe.dsc.caeto.core.application.Application;
import br.upe.dsc.caeto.core.cacheunit.CacheUnit;
import br.upe.dsc.caeto.core.cacheunit.CacheUnitExplorer;
import br.upe.dsc.caeto.core.cacheunit.CacheUnitSimple;
import br.upe.dsc.caeto.core.hierarchy.Hierarchy;
import br.upe.dsc.caeto.core.InvalidElementException;
import br.upe.dsc.caeto.utils.xml.ResultsXml;

import java.util.Iterator;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Scanner;

public class SimulationContext {

    protected static final int INSTRUCTION_CACHE = 1;
    protected static final int DATA_CACHE = 2;
    protected static final double CPU_STALL_ENERGY = 0.6946752;
    protected static final double MEMORY_ENERGY_PER_WORD = 0.0000000007560187;
    protected static final int PENALTY_16 = 44;        //em ciclos
    protected static final int PENALTY_32 = 60;        //em ciclos
    protected static final int PENALTY_64 = 92;        //em ciclos
    protected static final int TEMPO_ACERTO_NIVEL_2 = 5; //em ciclos
    protected static String pathSimCache = "/opt/simplescalar/simplesim-3.0/";
    protected static String pathEcacti = "/opt/eCACTI/";
    protected static int number = 0;
    protected static int ecactiNumber = 0;

    private static SimulationContext singleton;
    
    private ResultsXml resultXml;
    
    private SimulationContext() {
    	resultXml = new ResultsXml();
    }
    
    public static SimulationContext getInstance() {
    	return ((singleton == null) ? new SimulationContext() : singleton);
    }

    /*
     * Funcao que Valida a arquitetura de cache.
     * Caso seja valida retorna true, caso seja invalida retorna false
     */
    public static boolean validateCacheUnit(CacheUnit cacheUnit) {
        if (cacheUnit instanceof CacheUnitSimple) {
            int tamanhoCache, tamanhoLinha, associatividade;
            CacheUnitSimple cache = (CacheUnitSimple) cacheUnit;

            tamanhoCache = cache.getTamanho();
            tamanhoLinha = cache.getTamLinha();
            associatividade = cache.getAssociatividade();

            if ((tamanhoCache / (8 * tamanhoLinha * associatividade)) <= 0) {
                return false;
            }
        } else {
            int tamanhoCache[], tamanhoLinha[], associatividade[];
            CacheUnitExplorer cache = (CacheUnitExplorer) cacheUnit;

            tamanhoCache = cache.getTamanho();
            tamanhoLinha = cache.getTamLinha();
            associatividade = cache.getAssociatividade();

            if (associatividade[1] < associatividade[0]) return false;
            if (tamanhoLinha[1] < tamanhoLinha[0]) return false;
            if (tamanhoCache[1] < tamanhoCache[0]) return false;
            
            if ((tamanhoCache[0] /
            		(8 * tamanhoLinha[0] * associatividade[0]) <= 0) &&
                (tamanhoCache[1] /
                	(8 * tamanhoLinha[1] * associatividade[1]) <= 0)) {
                return false;
            }
        }
        return true;
    }
    
    public boolean validateHierarchy(SimulationPoint point) {
    	Hierarchy hierarchy = point.getHierarchy();
    	
    	if (hierarchy.getName().equals(Hierarchy.HIERARCHY_1)) {
    		return validateHierarchyOne(hierarchy);
    	}
    	if (hierarchy.getName().equals(Hierarchy.HIERARCHY_2)) {
    		return validateHierarchyTwo(hierarchy);
    	}
    	if (hierarchy.getName().equals(Hierarchy.HIERARCHY_4)) {
    		return validateHierarchyFour(hierarchy);
    	}
    	
    	return false;
    }
    
    private boolean validateHierarchyOne(Hierarchy hierarchy) {
    	float numConjuntosL1, numConjuntosL2;
    	float numConjuntosD1, numConjuntosD2;
    	
        /*
         * cache[0] - Cache de dados nivel 1
         * cache[1] - Cache de dados nivel 2
         * cache[2] - Cache de instrucao nivel 1
         * cache[3] - Cache de instrucao nivel 2
         */
    	CacheUnitSimple cache[] = new CacheUnitSimple[4];
    	
    	Iterable<CacheUnit> i = hierarchy.getCacheUnits();
    	for (CacheUnit c : i) {
    		if (c.getName().equals(CacheUnit.DATA_CACHE_LEVEL_1)) {
    			cache[0] = (CacheUnitSimple) c;
    		} else if (c.getName().equals(CacheUnit.DATA_CACHE_LEVEL_2)) {
    			cache[1] = (CacheUnitSimple) c;
    		} else if (c.getName().equals(
    				CacheUnit.INSTRUCTION_CACHE_LEVEL_1)) {
    			cache[2] = (CacheUnitSimple) c;
    		} else if (c.getName().equals(
    				CacheUnit.INSTRUCTION_CACHE_LEVEL_2)) {
    			cache[3] = (CacheUnitSimple) c;
    		}
    	}

    	numConjuntosD1 = (float) (cache[0].getTamanho() /
    			(cache[0].getTamLinha() * cache[0].getAssociatividade()));
    	numConjuntosD2 = (float) (cache[1].getTamanho() /
    			(cache[1].getTamLinha() * cache[1].getAssociatividade()));

    	/*Guarda o numero de conjuntos da cache de nivel 1*/
    	numConjuntosL1 = (float) (cache[2].getTamanho() /
    			(cache[2].getTamLinha() * cache[2].getAssociatividade()));

    	/*Guarda o numero de conjuntos da cache de nivel 2*/
    	numConjuntosL2 = (float) (cache[3].getTamanho() /
    			(cache[3].getTamLinha() * cache[3].getAssociatividade()));

    	if (cache[1].getTamLinha() < cache[0].getTamLinha()) {
    		return false;
    	}
    	if (cache[3].getTamLinha() < cache[2].getTamLinha()) {
    		return false;
    	}

    	/*
    	 * Verifica se o tamanho da linha de nivel 2
    	 * eh maior ou igual aa de nivel 1
    	 */
    	if (cache[1].getTamanho() <= cache[0].getTamanho()) {
    		return false;
    	}

    	/*
    	 * verifica se o tamanho da cache de nivel 2
    	 * eh maior do que a de nivel 1
    	 */
    	if (cache[3].getTamanho() <= cache[2].getTamanho()) {
    		return false;
    	}
    	if (numConjuntosD2 < numConjuntosD1) {
    		return false;
    	}

    	/*
    	 * verifica se o numero de conjuntos de nivel 2
    	 * eh maior do que o de nivel 1
    	 */
    	if (numConjuntosL2 < numConjuntosL1) {
    		return false;
    	}
    	
    	return true;
    }
    
    private boolean validateHierarchyTwo(Hierarchy hierarchy) {
    	float numConjuntosL1, numConjuntosD1, numConjuntosD2;
    	
    	/*
    	 * cache[0] - Cache de dados nivel 1
    	 * cache[1] - Cache de dados nivel 2
    	 * cache[2] - Cache de instrucao nivel 1
    	 */
    	CacheUnitSimple cache[] = new CacheUnitSimple[3];
    	
    	Iterable<CacheUnit> i = hierarchy.getCacheUnits();
    	for (CacheUnit c : i) {
    		if (c.getName().equals(CacheUnit.DATA_CACHE_LEVEL_1)) {
    			cache[0] = (CacheUnitSimple) c;
    		} else if (c.getName().equals(CacheUnit.DATA_CACHE_LEVEL_2)) {
    			cache[1] = (CacheUnitSimple) c;
    		} else if (c.getName().equals(
    				CacheUnit.INSTRUCTION_CACHE_LEVEL_1)) {
    			cache[2] = (CacheUnitSimple) c;
    		} else if (c.getName().equals(
    				CacheUnit.INSTRUCTION_CACHE_LEVEL_2)) {
    			cache[3] = (CacheUnitSimple) c;
    		}
    	}

    	numConjuntosD1 = (float) (cache[0].getTamanho() /
    			(cache[0].getTamLinha() * cache[0].getAssociatividade()));
    	numConjuntosD2 = (float) (cache[1].getTamanho() /
    			(cache[1].getTamLinha() * cache[1].getAssociatividade()));

    	/*Guarda o numero de conjuntos da cache de nivel 1*/
    	numConjuntosL1 = (float) (cache[2].getTamanho() /
    			(cache[2].getTamLinha() * cache[2].getAssociatividade()));

    	if (cache[2].getTamLinha() < cache[0].getTamLinha()) {
    		return false;
    	}
    	if (cache[2].getTamLinha() < cache[1].getTamLinha()) {
    		return false;
    	}

    	/*
    	 * Verifica se o tamanho da linha de nivel 2
    	 * eh maior ou igual a de nivel 1
    	 */
    	if (cache[2].getTamanho() <= cache[0].getTamanho()) {
    		return false;
    	}

    	/*
    	 * Verifica se o tamanho da cache de nivel 2
    	 * eh maior do que a de nivel 1
    	 */
    	if (cache[2].getTamanho() <= cache[1].getTamanho()) {
    		return false;
    	}
    	if (numConjuntosL1 < numConjuntosD1) {
    		return false;
    	}

    	/*
    	 * verifica se o numero de conjuntos de nivel 2
    	 * eh maior do que o de nivel 1
    	 */
    	if (numConjuntosL1 < numConjuntosD2) {
    		return false;
    	}
    	return true;
    }
    
    private boolean validateHierarchyFour(Hierarchy hierarchy) {
        float numConjuntosL1, numConjuntosL2;
        
        /*
         * cache[0] - Cache de dados nivel 1
         * cache[1] - Cache de instrucao nivel 1
         */
    	CacheUnitSimple cache[] = new CacheUnitSimple[2];
    	
    	Iterable<CacheUnit> i = hierarchy.getCacheUnits();
    	for (CacheUnit c : i) {
    		if (c.getName().equals(CacheUnit.DATA_CACHE_LEVEL_1)) {
    			cache[0] = (CacheUnitSimple) c;
    		} else if (c.getName().equals(
    				CacheUnit.INSTRUCTION_CACHE_LEVEL_1)) {
    			cache[1] = (CacheUnitSimple) c;
    		}
    	}

        /*Guarda o numero de conjuntos da cache de nivel 1*/
        numConjuntosL1 = (float) (cache[0].getTamanho() /
        		(cache[0].getTamLinha() * cache[0].getAssociatividade()));
        
        /*Guarda o numero de conjuntos da cache de nivel 2*/
        numConjuntosL2 = (float) (cache[1].getTamanho() /
        		(cache[1].getTamLinha() * cache[1].getAssociatividade()));

        /*
         * Verifica se o tamanho da linha de nivel 2
         * eh maior ou igual a de nivel 1
         */
        if (cache[1].getTamLinha() < cache[0].getTamLinha()) {
            return false;
        }
        
        /*
         * Verifica se o tamanho da cache de nivel 2
         * eh maior do que a de nivel 1
         */
        if (cache[1].getTamanho() <= cache[0].getTamanho()) {
            return false;
        }
        
        /*
         * Verifica se o numero de conjuntos de nivel 2
         * eh maior do que o de nivel 1
         */
        if (numConjuntosL2 < numConjuntosL1) {
            return false;
        }
    	return true;
    }
    
    public int getCacheAssociatividadeMinimo(Application aplicacao,
    		String nome) {
        Iterator<CacheUnit> iterator = (Iterator<CacheUnit>)
        	aplicacao.getHierarquia().getCacheUnits();
        CacheUnitExplorer cache;

        while (iterator.hasNext()) {
            cache = (CacheUnitExplorer) iterator.next();
            if (cache.getName().equals(nome)) {
                return cache.getAssociatividade()[0];
            }
        }
        return 0;
    }

    public int getCacheAssociatividadeMaximo(Application aplicacao,
    		String nome) {
        Iterator<CacheUnit> iterator = (Iterator<CacheUnit>)
        	aplicacao.getHierarquia().getCacheUnits();
        CacheUnitExplorer cache;

        while (iterator.hasNext()) {
            cache = (CacheUnitExplorer) iterator.next();
            if (cache.getName().equals(nome)) {
                return cache.getAssociatividade()[1];
            }
        }
        return 0;
    }

    public int[] getCacheAssociatividade(Application aplicacao, String nome) {
        Iterator<CacheUnit> iterator = (Iterator<CacheUnit>)
        	aplicacao.getHierarquia().getCacheUnits();
        CacheUnitExplorer cache;

        while (iterator.hasNext()) {
            cache = (CacheUnitExplorer) iterator.next();
            if (cache.getName().equals(nome)) {
                return cache.getAssociatividade();
            }
        }
        return null;
    }

    public int getCacheTamanhoMinimo(Application aplicacao, String nome) {
        Iterator iterator = (Iterator<CacheUnit>)
        	aplicacao.getHierarquia().getCacheUnits();
        CacheUnitExplorer cache;

        while (iterator.hasNext()) {
            cache = (CacheUnitExplorer) iterator.next();
            if (cache.getName().equals(nome)) {
                return cache.getTamanho()[0];
            }
        }

        return 0;
    }

    public int getCacheTamanhoMaximo(Application aplicacao, String nome) {
        Iterator iterator = (Iterator<CacheUnit>)
        	aplicacao.getHierarquia().getCacheUnits();
        CacheUnitExplorer cache;

        while (iterator.hasNext()) {
            cache = (CacheUnitExplorer) iterator.next();
            if (cache.getName().equals(nome)) {
                return cache.getTamanho()[1];
            }
        }
        return 0;
    }

    public int[] getCacheTamanho(Application aplicacao, String nome) {
        Iterator iterator = (Iterator<CacheUnit>)
        	aplicacao.getHierarquia().getCacheUnits();
        CacheUnitExplorer cache;

        while (iterator.hasNext()) {
            cache = (CacheUnitExplorer) iterator.next();
            if (cache.getName().equals(nome)) {
                return cache.getTamanho();
            }
        }
        return null;
    }

    public int getCacheLinhaMinimo(Application aplicacao, String nome) {
        Iterator iterator = (Iterator<CacheUnit>)
        	aplicacao.getHierarquia().getCacheUnits();
        CacheUnitExplorer cache;

        while (iterator.hasNext()) {
            cache = (CacheUnitExplorer) iterator.next();
            if (cache.getName().equals(nome)) {
                return cache.getTamLinha()[0];
            }
        }
        return 0;
    }

    public int getCacheLinhaMaximo(Application aplicacao, String nome) {
        Iterator iterator = (Iterator<CacheUnit>)
        	aplicacao.getHierarquia().getCacheUnits();
        CacheUnitExplorer cache;

        while (iterator.hasNext()) {
            cache = (CacheUnitExplorer) iterator.next();
            if (cache.getName().equals(nome)) {
                return cache.getTamLinha()[1];
            }
        }
        return 0;
    }

    public int[] getCacheLinha(Application aplicacao, String nome) {
        Iterator iterator = (Iterator<CacheUnit>)
        	aplicacao.getHierarquia().getCacheUnits();
        CacheUnitExplorer cache;

        while (iterator.hasNext()) {
            cache = (CacheUnitExplorer) iterator.next();
            if (cache.getName().equals(nome)) {
                return cache.getTamLinha();
            }
        }
        return null;
    }
    
    /* Simuladores sim-cache e Ecacti */
    private void execEcacti(int tamanhoCache, int tamanhoLinha,
    		int associatividade) {
        String argument = tamanhoCache + " " + tamanhoLinha + " " +
        	associatividade + " 0.18 1";
        String line, tmp[];
        int ndwl = 0, nspd = 0, ndbl = 0, ntwl = 0, ntspd = 0, ntbl = 0;

        try {
            Process process = Runtime.getRuntime()
            	.exec("/opt/eCACTI/ecacti " + argument);
            BufferedReader input = new BufferedReader(
            		new InputStreamReader(process.getInputStream()));
            while ((line = input.readLine()) != null) {
                tmp = line.split("\\s");
                if (tmp[0].equals("Optimal") &&
                		tmp[1].equals("Power-AccessTime-Area")) {
                    ndwl = Integer.parseInt(tmp[3].trim().replace(",", "")
                    		.replace("(", ""));
                    nspd = Integer.parseInt(tmp[4].trim().replace(",", ""));
                    ndbl = Integer.parseInt(tmp[5].trim().replace(",", ""));
                    ntwl = Integer.parseInt(tmp[6].trim().replace(",", ""));
                    ntspd = Integer.parseInt(tmp[7].trim().replace(",", ""));
                    ntbl = Integer.parseInt(tmp[8].trim().replace(")", ""));
                }
            }
            input.close();
            ecactiNumber += 1;
            String file = "/tmp/ecacti_parametros" + ecactiNumber + ".txt";
            FileWriter writer = new FileWriter(new File(file), true);
            PrintWriter saida = new PrintWriter(writer, true);
            saida.printf("-verbose\n");
            saida.printf("-config %d:%d:%d:%d:%d:%d", ndwl, nspd, ndbl, ntwl,
            		ntspd, ntbl);
            saida.close();
            
            process = Runtime.getRuntime()
            	.exec("/opt/eCACTI/ecacti " + argument + " " + file);
            input = new BufferedReader(new InputStreamReader(
            		process.getInputStream()));
            writer = new FileWriter(new File("/tmp/caeto_ecacti.txt"), true);
            saida = new PrintWriter(writer, true);

            while ((line = input.readLine()) != null) {
                saida.println(line);
            }
            saida.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void execSimCache(Application aplicacao,
            int associatividadeil1, int tamanhoLinhail1, int tamanhoil1,
            int associatividadedl1, int tamanhoLinhadl1, int tamanhodl1,
            int associatividadeil2, int tamanhoLinhail2, int tamanhoil2,
            int associatividadedl2, int tamanhoLinhadl2, int tamanhodl2) {

        String argument = "-redir:sim " + aplicacao.getArquivoSaida() +
                " -cache:il1 il1:" + associatividadeil1 + ":" +
                tamanhoLinhail1 + ":" + tamanhoil1 + ":r " + "-cache:dl1 dl1:" +
                associatividadedl1 + ":" + tamanhoLinhadl1 + ":" + tamanhodl1 +
                ":r -cache:il2 il2:" + associatividadeil2 + ":" +
                tamanhoLinhail2 + ":" + tamanhoil2 + ":r -cache:dl2 dl2:" +
                associatividadedl2 + ":" + tamanhoLinhadl2 + ":" + tamanhodl2 +
                ":r " + aplicacao.getBenchMark() + " " +
                aplicacao.getArquivoEntrada() + " > caeto_output.txt";

        try {
            Process process = Runtime.getRuntime().exec(pathSimCache +
            		"sim-cache" + " " + argument);
            process.waitFor();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void execSimCache(Application aplicacao,
            int associatividadeil1, int tamanhoLinhail1, int tamanhoil1,
            int associatividadedl1, int tamanhoLinhadl1, int tamanhodl1,
            int associatividadeil2, int tamanhoLinhail2, int tamanhoil2) {
    	/* Para hierarquia 2 */

        String argument = "-redir:sim " + aplicacao.getArquivoSaida() +
                " -cache:il1 il1:" + associatividadeil1 + ":" +
                tamanhoLinhail1 + ":" + tamanhoil1 + ":r -cache:dl1 dl1:" +
                associatividadedl1 + ":" + tamanhoLinhadl1 + ":" + tamanhodl1 +
                ":r -cache:il2 il2:" + associatividadeil2 + ":" +
                tamanhoLinhail2 + ":" + tamanhoil2 + ":r " +
                aplicacao.getBenchMark() + " " + aplicacao.getArquivoEntrada() +
                " > caeto_output.txt";

        try {
            Process process = Runtime.getRuntime()
            	.exec(pathSimCache + "sim-cache" + " " + argument);
            process.waitFor();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void execSimCache(Application aplicacao,
            int associatividadeil1, int tamanhoLinhail1, int tamanhoil1,
            int associatividadedl1, int tamanhoLinhadl1, int tamanhodl1) {
        /* Para hierarquia 3 e hierarquia 4 */
        
        int numberSetsil1 = tamanhoil1 / tamanhoLinhail1;
        int numberSetsdl1 = tamanhodl1 / tamanhoLinhadl1;
        System.out.println("# " + number);
        System.out.println("Sets il1: " + numberSetsil1);
        System.out.println("Sets dl1: " + numberSetsdl1);   
        
        String argument = "-redir:sim " + aplicacao.getArquivoSaida() +
                " -cache:il1 il1:" + numberSetsil1 + ":" + tamanhoLinhail1 +
                ":" + associatividadeil1 + ":r -cache:dl1 dl1:" +
                numberSetsdl1 + ":" + tamanhoLinhadl1 + ":" +
                associatividadedl1 + ":r " + aplicacao.getBenchMark() + " " +
                aplicacao.getArquivoEntrada() + " > caeto_output.txt";

        try {
            Process process = Runtime.getRuntime()
            	.exec(pathSimCache + "sim-cache" + " " + argument);
            process.waitFor();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /* Calculo de energia e ciclos */
    protected double[] energyCycles(int tamanhoLinha,
    		float ecactiResults[], float simcacheResults[]) {

        int penalidadeCache;
        float hits, accessNumber, misses;
        double missRate, acessTime, pdyn, pleak, edyn, eleak;
        double wordsRead, memoryEnergy, missCycle, totalCycles;
        double cacheEnergy, totalEnergy;

        hits = simcacheResults[0];
        accessNumber = simcacheResults[2];
        misses = simcacheResults[4];
        missRate = misses / accessNumber;

        acessTime = ecactiResults[0];
        pdyn = ecactiResults[1];
        pleak = ecactiResults[2];

        edyn = pdyn * acessTime * 0.001;
        eleak = pleak * acessTime * 0.001;

        penalidadeCache = penalidade(tamanhoLinha);

        wordsRead = missRate * accessNumber * tamanhoLinha;
        missCycle = missRate * accessNumber * penalidadeCache;
        totalCycles = accessNumber + missCycle;
        memoryEnergy = wordsRead * MEMORY_ENERGY_PER_WORD;
        cacheEnergy = (tamanhoLinha * edyn + totalCycles * eleak +
        		missCycle * CPU_STALL_ENERGY) / 1000000000;
        totalEnergy = memoryEnergy + cacheEnergy;

        try {
            String path = "/tmp/";
            String fileName = "caeto_log.txt";
            FileWriter writer = new FileWriter(new File(path + fileName), true);
            PrintWriter saida = new PrintWriter(writer, true);
            
            saida.printf("\npenalidade: %d\nhits: %f\naccessNumber: %f\n" +
            		"misses: %f\nmissRate: %f\n", penalidadeCache, hits,
            		accessNumber, misses, missRate);
            
            saida.printf("\naccessTime: %f\npdyn: %f\npleak: %f\nedyn: %f\n" +
            		"eleak: %f\n", acessTime, pdyn, pleak, edyn, eleak);

            saida.printf("\nwordsRead %f\nmissCycle: %f\nmemoryEnergy: %f\n" +
            		"cacheEnergy: %f\n", wordsRead, missCycle,
            		memoryEnergy, cacheEnergy);
        } catch (IOException e) {
            e.printStackTrace();
        }

        double results[] = {totalEnergy, totalCycles};

        return results;
    }

    protected double[] energyCycles(int tamanhoLinhaNivel1,
    		int tamanhoLinhaNivel2, float ecactiResultsNivel1[],
            float ecactiResultsNivel2[], float simcacheResults[]) {
        float hitsNivel2, accessesNumberNivel1, accessesNumberNivel2;
        float missesNivel2, missRateNivel2, custoFaltaNivel1, custoFaltaNivel2;
        float totalCyclesNivel1, totalCyclesNivel2;

        double acessTimeNivel1, pdynNivel1, pleakNivel1;
        double edynNivel1, eleakNivel1, acessTimeNivel2, pdynNivel2;
        double pleakNivel2, edynNivel2, eleakNivel2;

        double energyMemoryAcess, memoryWordsRead, memoryEnergy;
        double energyNivel1, energyNivel2, totalEnergy, totalCycles;

        int penalidadeNivel2;

        /* Parametros sim-cache */
        hitsNivel2 = simcacheResults[1];
        accessesNumberNivel1 = simcacheResults[2];
        accessesNumberNivel2 = simcacheResults[3];
        missesNivel2 = simcacheResults[5];
        missRateNivel2 = simcacheResults[7];

        /* Parametros eCacti */
        acessTimeNivel1 = ecactiResultsNivel1[0];
        pdynNivel1 = ecactiResultsNivel1[1];
        pleakNivel1 = ecactiResultsNivel1[2];
        acessTimeNivel2 = ecactiResultsNivel2[0];
        pdynNivel2 = ecactiResultsNivel2[1];
        pleakNivel2 = ecactiResultsNivel2[2];

        edynNivel1 = pdynNivel1 * acessTimeNivel1 * 0.001;
        eleakNivel1 = pleakNivel1 * acessTimeNivel1 * 0.001;
        edynNivel2 = pdynNivel2 * acessTimeNivel2 * 0.001;
        eleakNivel2 = pleakNivel2 * acessTimeNivel2 * 0.001;

        penalidadeNivel2 = penalidade(tamanhoLinhaNivel2);

        custoFaltaNivel2 = penalidadeNivel2 * missesNivel2;
        custoFaltaNivel1 = (hitsNivel2 * TEMPO_ACERTO_NIVEL_2) +
        	(missRateNivel2 * custoFaltaNivel2);

        energyMemoryAcess = 3.1 * (edynNivel2 + eleakNivel2);
        memoryWordsRead = tamanhoLinhaNivel2 * missesNivel2;

        totalCyclesNivel1 = custoFaltaNivel1 + accessesNumberNivel1;
        totalCyclesNivel2 = custoFaltaNivel2 + accessesNumberNivel2;

        memoryEnergy = (energyMemoryAcess * memoryWordsRead) / 1000000000;
        energyNivel2 = (((accessesNumberNivel2 * edynNivel2) +
        		(totalCyclesNivel2 * eleakNivel2) +
        			(custoFaltaNivel2 * CPU_STALL_ENERGY)) / 1000000000);
        energyNivel1 = (((accessesNumberNivel1 * edynNivel1) +
        		(totalCyclesNivel1 * eleakNivel1) +
            		(custoFaltaNivel1 * CPU_STALL_ENERGY)) / 1000000000);

        totalEnergy = memoryEnergy + energyNivel2 + energyNivel1;
        totalCycles = totalCyclesNivel1 + totalCyclesNivel2;

        double result[] = {totalEnergy, totalCycles};
        return result;
    }

    private int penalidade(int tamanhoLinha) {
        int penalty = 1;
        switch (tamanhoLinha) {
            case (16):
                penalty = PENALTY_16;
                break;
            case (32):
                penalty = PENALTY_32;
                break;
            case (64):
                penalty = PENALTY_64;
                break;
        }
        return penalty;
    }

    /* Manipulacao de arquivos */
    protected float[] extractInformationsEcacti() {
        float accessTime = 0, pdyn = 0, pleak = 0;
        try {
            BufferedReader buffer = new BufferedReader(
            		new FileReader("/tmp/caeto_ecacti.txt"));
            Scanner readFile = new Scanner(buffer);
            String input;
            String tmp[];
            int i, length;

            //Lê o arquivo
            while (readFile.hasNextLine()) {
                input = readFile.nextLine();
                tmp = input.split("\\s");
                length = tmp.length;
                if (tmp[0].equals("Access") && tmp[1].equals("time:")) {
                    accessTime = Float.parseFloat(tmp[2]);
                } else if (input.split(":")[0].trim()
                		.equals("Total Power all Banks")) {
                    for (i = 7; i < length && tmp[i].equals(""); i++);
                    pdyn = Float.parseFloat(tmp[i].replace(",", ""));
                    for (i = i + 1; i < tmp.length && tmp[i].equals(""); i++);
                    pleak = Float.parseFloat(tmp[i]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        float retorno[] = {accessTime, pdyn, pleak};
        return retorno;
    }

    protected float[] extractInformationsSimCache(Application aplicacao,
    		int cache, int numberLevels) {
        float hitsNivel1 = 0, hitsNivel2 = 0, acessosNivel1 = 0;
        float acessosNivel2 = 0, missesNivel1 = 0, missesNivel2 = 0;
        float missRateNivel1 = 0, missRateNivel2 = 0;
        
        try {
            BufferedReader buffer = new BufferedReader(
            		new FileReader(aplicacao.getArquivoSaida()));
            Scanner readFile = new Scanner(buffer);
            String input;
            String tmp[];
            int i, length;

            if (cache == INSTRUCTION_CACHE) {
                while (readFile.hasNextLine()) {
                    input = readFile.nextLine();
                    tmp = input.split("\\s");
                    length = tmp.length;
                    if (tmp[0].equals("il1.accesses")) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        acessosNivel1 = Float.parseFloat(tmp[i]);
                    } else if (tmp[0].equals("il1.hits")) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        hitsNivel1 = Float.parseFloat(tmp[i]);
                    } else if (tmp[0].equals("il1.misses")) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        missesNivel1 = Float.parseFloat(tmp[i]);
                    } else if (tmp[0].equals("il1.miss_rate")) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        missRateNivel1 = Float.parseFloat(tmp[i]);
                    }
                    else if ((numberLevels == 2) &&
                    		(tmp[0].equals("il2.accesses"))) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        acessosNivel2 = Float.parseFloat(tmp[i]);
                    } else if ((numberLevels == 2) &&
                    		(tmp[0].equals("il2.hits"))) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        hitsNivel2 = Float.parseFloat(tmp[i]);
                    } else if ((numberLevels == 2) &&
                    		(tmp[0].equals("il2.misses"))) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        missesNivel2 = Float.parseFloat(tmp[i]);
                    } else if ((numberLevels == 2) &&
                    		(tmp[0].equals("il2.miss_rate"))) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        missRateNivel2 = Float.parseFloat(tmp[i]);
                    }
                }
            } else if (cache == DATA_CACHE) {
                while (readFile.hasNextLine()) {
                    input = readFile.nextLine();
                    tmp = input.split("\\s");
                    length = tmp.length;
                    if (tmp[0].equals("dl1.accesses")) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        acessosNivel1 = Float.parseFloat(tmp[i]);
                    } else if (tmp[0].equals("dl1.hits")) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        hitsNivel1 = Float.parseFloat(tmp[i]);
                    } else if (tmp[0].equals("dl1.misses")) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        missesNivel1 = Float.parseFloat(tmp[i]);
                    } else if (tmp[0].equals("dl1.miss_rate")) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        missRateNivel1 = Float.parseFloat(tmp[i]);
                    }
                    else if ((numberLevels == 2) &&
                    		(tmp[0].equals("dl2.accesses"))) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        acessosNivel2 = Float.parseFloat(tmp[i]);
                    } else if ((numberLevels == 2) &&
                    		(tmp[0].equals("dl2.hits"))) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        hitsNivel2 = Float.parseFloat(tmp[i]);
                    } else if ((numberLevels == 2) &&
                    		(tmp[0].equals("dl2.misses"))) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        missesNivel2 = Float.parseFloat(tmp[i]);
                    } else if ((numberLevels == 2) &&
                    		(tmp[0].equals("dl2.miss_rate"))) {
                        for (i = 1; i < length && tmp[i].equals(""); i++);
                        missRateNivel2 = Float.parseFloat(tmp[i]);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        Result retult;
        
        float retorno[] = {hitsNivel1, hitsNivel2, acessosNivel1, acessosNivel2,
            missesNivel1, missesNivel2, missRateNivel1, missRateNivel2
        };
        return retorno;
    }

    public double[][] simular(Application aplicacao,
            int associatividadeil1, int tamanhoLinhail1, int tamanhoil1,
            int associatividadedl1, int tamanhoLinhadl1, int tamanhodl1,
            int associatividadeil2, int tamanhoLinhail2, int tamanhoil2,
            int associatividadedl2, int tamanhoLinhadl2, int tamanhodl2) {

        execSimCache(aplicacao, associatividadeil1, tamanhoLinhail1, tamanhoil1,
        		associatividadedl1, tamanhoLinhadl1, tamanhodl1,
        		associatividadeil2, tamanhoLinhail2, tamanhoil2,
        		associatividadedl2, tamanhoLinhadl2, tamanhodl2);

        float infoSimCacheDados[] = extractInformationsSimCache(
        		aplicacao, DATA_CACHE, 2);
        float infoSimCacheInstrucoes[] = extractInformationsSimCache(
        		aplicacao, INSTRUCTION_CACHE, 2);

        execEcacti(tamanhoil1, tamanhoLinhail1, associatividadeil1);
        float infoEcactiil1[] = extractInformationsEcacti();
        execEcacti(tamanhodl1, tamanhoLinhadl1, associatividadedl1);
        float infoEcactidl1[] = extractInformationsEcacti();
        execEcacti(tamanhoil2, tamanhoLinhail2, associatividadeil2);
        float infoEcactiil2[] = extractInformationsEcacti();
        execEcacti(tamanhodl1, tamanhoLinhadl2, associatividadedl2);
        float infoEcactidl2[] = extractInformationsEcacti();

        double resultsDados[] = energyCycles(tamanhoLinhadl1, tamanhoLinhadl2,
        		infoEcactidl1, infoEcactidl2, infoSimCacheDados);
        double resultsInstrucoes[] = energyCycles(tamanhoLinhail1,
        		tamanhoLinhail2, infoEcactiil1, infoEcactiil2,
        		infoSimCacheInstrucoes);

        double results[][] = new double[2][2];
        results[0][0] = resultsDados[0];
        results[0][1] = resultsDados[1];
        results[1][0] = resultsInstrucoes[0];
        results[1][1] = resultsInstrucoes[1];

        return results;
    }

    public double[][] simular(Application aplicacao,
            int associatividadeil1, int tamanhoLinhail1, int tamanhoil1,
            int associatividadedl1, int tamanhoLinhadl1, int tamanhodl1,
            int associatividadeil2, int tamanhoLinhail2, int tamanhoil2) {

        /* Ainda nao tem como calcular a energia e os ciclos */

        return null;
    }

    public Result simulate(Application application, SimulationPoint point) {
    	Result result = new Result();		
    	Hierarchy hierarchy = point.getHierarchy();
    	
    	if (hierarchy == null) return result; //Throws an exception
    	
    	if (hierarchy.getName().equals(Hierarchy.HIERARCHY_4)) {
    		
    	}
    	
    	return null;
    }
    
    public double[][] simular(Application aplicacao,
            int associatividadeil1, int tamanhoLinhail1, int tamanhoil1,
            int associatividadedl1, int tamanhoLinhadl1, int tamanhodl1) {

        number += 1;
        
        execSimCache(aplicacao, associatividadeil1, tamanhoLinhail1, tamanhoil1,
                associatividadedl1, tamanhoLinhadl1, tamanhodl1);

        float infoSimCacheDados[] = extractInformationsSimCache(
        		aplicacao, DATA_CACHE, 1);
        float infoSimCacheInstrucoes[] = extractInformationsSimCache(
        		aplicacao, INSTRUCTION_CACHE, 1);

        execEcacti(tamanhoil1, tamanhoLinhail1, associatividadeil1);
        float infoEcactiInstrucoes[] = extractInformationsEcacti();
        execEcacti(tamanhodl1, tamanhoLinhadl1, associatividadedl1);
        float infoEcactiDados[] = extractInformationsEcacti();
        
        double resultsDados[] = null, resultsInstrucoes[] = null;
        
        try {
            String path = "/tmp/";
            String fileName = "caeto_log.txt";
            FileWriter writer = new FileWriter(new File(path + fileName), true);
            PrintWriter saida = new PrintWriter(writer, true);
            
            saida.printf("\n#%d il1 %d:%d:%d dl1 %d:%d:%d\n",
                    number, tamanhoil1, associatividadeil1, tamanhoLinhail1,
                    tamanhodl1, associatividadedl1, tamanhoLinhadl1);
            
            saida.printf("\nCache dl1:\n");
            
            resultsDados = energyCycles(tamanhoLinhadl1, infoEcactiDados,
            		infoSimCacheDados);
            
            saida.printf("\nCache il1:\n");
            
            resultsInstrucoes = energyCycles(tamanhoLinhail1,
            		infoEcactiInstrucoes, infoSimCacheInstrucoes);
            
            saida.printf("\n--------------------------------------\n");
        } catch (IOException e) {
            e.printStackTrace();
        }

        double results[][] = new double[2][2];
        results[0][0] = resultsDados[0];
        results[0][1] = resultsDados[1];
        results[1][0] = resultsInstrucoes[0];
        results[1][1] = resultsInstrucoes[1];
        
        return results;
    }

    /*
    public void gravarSimulacao(Application aplicacao,
            int associatividadeil1, int tamanhoLinhail1, int tamanhoil1,
            int associatividadedl1, int tamanhoLinhadl1, int tamanhodl1,
            int associatividadeil2, int tamanhoLinhail2, int tamanhoil2,
            int associatividadedl2, int tamanhoLinhadl2, int tamanhodl2,
            double[][] resultados) {

        try {
            String path = "/tmp/";
            String fileName = "caeto.txt";
            FileWriter writer = new FileWriter(new File(path + fileName), true);
            PrintWriter saida = new PrintWriter(writer, true);
            saida.printf("\n");
            
            // Cabecalho
            saida.printf("il1 %d:%d:%d dl1 %d:%d:%d il2 %d:%d:%d dl2 %d:%d:%d\n",
                    associatividadeil1, tamanhoLinhail1, tamanhoil1,
                    associatividadedl1, tamanhoLinhadl1, tamanhodl1,
                    associatividadeil2, tamanhoLinhail2, tamanhoil2,
                    associatividadedl2, tamanhoLinhadl2, tamanhodl2);

            // Resultados
            saida.printf("data.energy: %f\n", resultados[0][0]);
            saida.printf("data.cycles: %f\n", resultados[0][1]);
            saida.printf("instruction.energy: %f\n", resultados[1][0]);
            saida.printf("instruction.cycles: %f\n", resultados[1][1]);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void gravarSimulacao(Application aplicacao,
            int associatividadeil1, int tamanhoLinhail1, int tamanhoil1,
            int associatividadedl1, int tamanhoLinhadl1, int tamanhodl1,
            int associatividadeil2, int tamanhoLinhail2, int tamanhoil2,
            double[][] resultados) {


    }

    public void gravarSimulacao(Application aplicacao,
            int associatividadeil1, int tamanhoLinhail1, int tamanhoil1,
            int associatividadedl1, int tamanhoLinhadl1, int tamanhodl1,
            double[][] resultados) {

        try {
            String path = "/tmp/";
            String fileName = "caeto.txt";
            FileWriter writer = new FileWriter(new File(path + fileName), true);
            PrintWriter saida = new PrintWriter(writer, true);
            
            // Cabecalho
            saida.printf("\n#%d il1 %d:%d:%d dl1 %d:%d:%d\n\n",
                    number, tamanhoil1, associatividadeil1, tamanhoLinhail1,
                    tamanhodl1, associatividadedl1, tamanhoLinhadl1);

            // Resultados
            saida.printf("instruction.cycles: %f\n", resultados[1][1]);
            saida.printf("instruction.energy: %f\n", resultados[1][0]);
            saida.printf("data.cycles: %f\n", resultados[0][1]);
            saida.printf("data.energy: %f\n", resultados[0][0]);
                        
            saida.printf("\n--------------------------------------\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    */
    
    public void saveSimulation(Application application, Result result) {
    	resultXml.write(application, result);
    }
}
